<?php

namespace App\Libraries\AppSocialite\Providers;

use App\Libraries\AppSocialite\User;
use Firebase\JWT\JWK;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Lcobucci\Clock\SystemClock;
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Signer\Rsa\Sha256;
use Lcobucci\JWT\Validation\Constraint\IssuedBy;
use Lcobucci\JWT\Validation\Constraint\SignedWith;
use Lcobucci\JWT\Validation\Constraint\ValidAt;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;

class AppleProvider extends AbstractProvider
{
    private const URL = 'https://appleid.apple.com';

    /**
     * Verify Apple jwt token.
     *
     * @param string $token
     *
     * @return array
     *
     * @see https://appleid.apple.com/auth/keys
     */
    public function verify($token)
    {
        $jwt = Configuration::forUnsecuredSigner();
        $token = $jwt->parser()->parse($token);

        $data = Cache::remember('appsocialite:Apple-JWKSet', 5 * 60, function () {
            return Http::get(self::URL . '/auth/keys')->json();
        });

        $publicKeys = JWK::parseKeySet($data);

        $kid = $token->headers()->get('kid');

        if (isset($publicKeys[$kid])) {
            $publicKey = openssl_pkey_get_details($publicKeys[$kid]);
            $constraints = [
                new SignedWith(new Sha256, InMemory::plainText($publicKey['key'])),
                new IssuedBy(self::URL),
                new ValidAt(SystemClock::fromUTC()),
            ];

            return $jwt->validator()->validate($token, ...$constraints);
        }

        throw new UnauthorizedHttpException('dingo', 'Invalid JWT Signature');
    }

    /**
     * {@inheritdoc}
     */
    protected function getUserByToken($token)
    {
        $this->verify($token);

        $claims = explode('.', $token)[1];

        return json_decode(base64_decode($claims), true);
    }

    /**
     * Get the User instance for the authenticated user.
     *
     * @return \App\Libraries\AppSocialite\Contracts\User
     */
    public function user()
    {
        $user = $this->getUserByToken($this->request['id_token']);

        return $this->mapUserToObject($user);
    }

    /**
     * {@inheritdoc}
     */
    protected function mapUserToObject(array $user)
    {
        return (new User())
            ->setRaw($user)
            ->map([
                'id' => $user['sub'],
                'name' => $this->request['name'] ?? '',
                'email' => $user['email'] ?? null,
            ]);
    }
}
